using System;
using System.Collections;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using CustomToolGenerator;
using EnvDTE;
using Neo.Generator.CodeGen;
using Neo.MetaModel.Reader;
using Webbness.Generator.CodeDom;
using Webbness.Module.VsTool.Interfaces;
using Debugger=System.Diagnostics.Debugger;
using StatusBar=EnvDTE.StatusBar;

namespace Webbness.Module.VsTool
{
    [Guid("718F3F2B-41DA-4763-93DA-B0CEB180374D")]
    public class CodeGenAdaptor : BaseCodeGeneratorWithSite
    {
        private readonly string[] folderGUIDs = new string[3] {"{6BB5F8EF-4483-11D3-8BCF-00C04F8EC28C}", "{6BB5F8F0-4483-11D3-8BCF-00C04F8EC28C}", "{66A26722-8FB5-11D2-AA7E-00C04F688DDE}"};
        private string[] _overwriteArray = new string[0];

        protected override byte[] GenerateCode(string path, string contents)
        {
            StringWriter writer;
            CodeGenerator codeGen;
            byte[] output;
            bool justTheSupportClasses = true;
            writer = new StringWriter();

            ValidModelFactory factory = new ValidModelFactory();
            ValidatingModel validmodel = factory.Create(path);
            if (validmodel.IsValid)
            {
                if (validmodel.Properties.ContainsKey("Debug") && validmodel.Properties["Debug"].Equals("true", StringComparison.InvariantCultureIgnoreCase))
                {
                    Debugger.Launch();
                }

                if (validmodel.Properties.ContainsKey("supportOnly") && validmodel.Properties["supportOnly"].Equals("false", StringComparison.InvariantCultureIgnoreCase))
                {
                    justTheSupportClasses = false;
                }

                if (validmodel.Properties.ContainsKey("forceOverwrite"))
                {
                    processOverwrites(validmodel.Properties["forceOverwrite"]);
                }
                else
                {
                    _overwriteArray = new string[0];
                }

                if (validmodel.HasForcedOverwrite)
                {
                    if (DialogResult.OK != MessageBox.Show("The following files will be overwritten:" + Environment.NewLine + validmodel.Message + Environment.NewLine + "Do you want to continue?", "Files will be Replaced!", MessageBoxButtons.OKCancel))
                    {
                        return null;
                    }
                }

                if (_overwriteArray.Length > 0)
                {
                    if (DialogResult.OK != MessageBox.Show("All the files of the following types will be overwritten:" + Environment.NewLine + String.Join(Environment.NewLine, _overwriteArray) + Environment.NewLine + "Do you want to continue?", "Files will be Replaced!", MessageBoxButtons.OKCancel))
                    {
                        return null;
                    }
                }

                ProjectItem modelItem = base.GetService(typeof (ProjectItem)) as ProjectItem;

                if (modelItem == null)
                {
                    MessageBox.Show("Unable to locate a ProjectItem based on the model.", "WebbnessCodeGen");
                    writer.Write("/* Unable to locate a ProjectItem based on the model. */");
                    output = Encoding.ASCII.GetBytes(writer.ToString());
                    writer.Close();
                    return output;
                }

                StatusBar sbar = modelItem.ContainingProject.DTE.Application.StatusBar;

                string modelFolder = Path.Combine(Path.GetDirectoryName(path), "Models");
                string solutionFolder = Path.GetDirectoryName(modelItem.DTE.Solution.FullName);
                string resourceFolder = Path.Combine(solutionFolder, "ModuleTemplates");

                codeGen = new CodeGenerator();
                codeGen.ReaderType = typeof (NorqueReader);
                codeGen.ResourcePath = resourceFolder;

                try
                {
                    codeGen.GenerateSupportClasses(path, writer);
                    sbar.Text = "Generated the support classes...";
                    if (justTheSupportClasses)
                    {
                        output = Encoding.ASCII.GetBytes(writer.ToString());
                        writer.Close();
                        return output;
                    }
                }
                catch (Exception e)
                {
                    MessageBox.Show(e.Message, "WebbnessCodeGen");
                    writer.WriteLine("/* WebbnessCodeGen caught an exception while generating the source code: {0}\n\n{1}\n*/", e.Message, e.StackTrace);
                }
                try
                {
                    IList modelFileList;
                    
                    if (Directory.Exists(modelFolder) == false)
                    {
                    	Directory.CreateDirectory(modelFolder);
                    }

                    modelFileList = codeGen.GenerateClassFiles(path, modelFolder, true, getOverwrite("model"));

                    sbar.Text = "Generated the model classes...";
                    Application.DoEvents();

                    foreach (string file in modelFileList)
                    {
                        AddFileToProjectIfNecessary(modelItem.ContainingProject, file);
                    }

                    codeGen = new CodeGenerator();
                    codeGen.ReaderType = typeof (NorqueReader);
                    codeGen.ResourcePath = resourceFolder;
                    codeGen.UserClassTemplate = "NeoSurrogate.vtl";
                    codeGen.UserFileExtension = "Surrogate.cs";
                    IList surrogateFileList;

                    surrogateFileList = codeGen.GenerateClassFiles(path, modelFolder, true, getOverwrite("surrogate"));

                    foreach (string file in surrogateFileList)
                    {
                        AddFileToProjectIfNecessary(modelItem.ContainingProject, file);
                    }

                    sbar.Text = "Generated the surrogate classes...";
                    Application.DoEvents();

                    Project moduleProject = modelItem.ContainingProject;

                    if (Directory.Exists(Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "Controllers")) == false)
                    	Directory.CreateDirectory(Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "Controllers"));
                    
                    if (Directory.Exists(Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "Controllers\\Database")) == false)
                    	Directory.CreateDirectory(Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "Controllers\\Database"));
                    
                    codeGen = new CodeGenerator();
                    codeGen.ReaderType = typeof (NorqueReader);
                    codeGen.ResourcePath = resourceFolder;
                    codeGen.UserClassTemplate = "NeoController.cs.vtl";
                    codeGen.UserFileExtension = "ControllerImpl.cs";

                    foreach (string file in codeGen.GenerateClassFiles(path, Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "Controllers\\Database"), true, getOverwrite("controller")))
                    {
                        AddFileToProjectIfNecessary(moduleProject, file);
                    }

                    sbar.Text = "Generated the database controller classes...";
                    Application.DoEvents();

                    
                    if (Directory.Exists(Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "Controllers\\Admin")) == false)
                    	Directory.CreateDirectory(Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "Controllers\\Admin"));
                    
                    codeGen = new CodeGenerator();
                    codeGen.ReaderType = typeof (NorqueReader);
                    codeGen.ResourcePath = resourceFolder;
                    codeGen.UserClassTemplate = "NeoAdminController.cs.vtl";
                    codeGen.UserFileExtension = "ControllerImpl.cs";

                    foreach (string file in codeGen.GenerateClassFiles(path, Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "Controllers\\Admin"), true, getOverwrite("controller")))
                    {
                        AddFileToProjectIfNecessary(moduleProject, file);
                    }

                    sbar.Text = "Generated the admin controller classes...";
                    Application.DoEvents();
                    
                    if (Directory.Exists(Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "Controllers\\Base")) == false)
                    	Directory.CreateDirectory(Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "Controllers\\Base"));
                    
                    codeGen = new CodeGenerator();
                    codeGen.ReaderType = typeof (NorqueReader);
                    codeGen.ResourcePath = resourceFolder;
                    codeGen.UserClassTemplate = "NeoBaseController.cs.vtl";
                    codeGen.UserFileExtension = "Base.cs";

                    foreach (string file in codeGen.GenerateClassFiles(path, Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "Controllers\\Base"), true, true))
                    {
                        AddFileToProjectIfNecessary(moduleProject, file);
                    }

                    sbar.Text = "Generated the base controller classes...";
                    Application.DoEvents();

                    if (Directory.Exists(Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "Controllers\\Helpers")) == false)
                    	Directory.CreateDirectory(Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "Controllers\\Helpers"));
                   
                    ISingleTemplateCreator helpers = new HelperFileCreator();
                    helpers.CreateFolders = true;
                    helpers.ForceOverwrite = getOverwrite("helper");
                    helpers.OutputFolder = Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "Controllers\\Helpers");
                    helpers.Model = validmodel.Model;
                    helpers.TemplatesFolder = resourceFolder;
                    helpers.Template = "NeoHelper.cs.vtl";
                    helpers.ProjectName = moduleProject.Name;
                    helpers.Run();
                    foreach (string file in helpers.CreatedFiles)
                    {
                        AddFileToProjectIfNecessary(moduleProject, file);
                    }

                    sbar.Text = "Generated the helper classes...";
                    Application.DoEvents();

                    if (Directory.Exists(Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "Controllers\\Resources")) == false)
                    	Directory.CreateDirectory(Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "Controllers\\Resources"));
                    
                    ISingleTemplateCreator resourceFileCreator = new SingleTemplateFileCreator();
                    resourceFileCreator.CreateFolders = true;
                    resourceFileCreator.ForceOverwrite = getOverwrite("resource");
                    resourceFileCreator.OutputFolder = String.Format("{0}\\Controllers\\Resources", Path.GetDirectoryName(moduleProject.FullName));
                    resourceFileCreator.Model = validmodel.Model;
                    resourceFileCreator.TemplatesFolder = resourceFolder;
                    resourceFileCreator.Template = "Resource.resx.vtl";
                    resourceFileCreator.ProjectName = moduleProject.Name;
                    resourceFileCreator.Run();
                    foreach (string file in resourceFileCreator.CreatedFiles)
                    {
                        AddFileToProjectIfNecessary(moduleProject, file);
                    }

                    sbar.Text = "Generated the resource files...";
                    Application.DoEvents();

                    ISingleTemplateCreator recursiveFileCreator = new RecursiveFileCreator();
                    recursiveFileCreator.CreateFolders = true;
                    recursiveFileCreator.ForceOverwrite = false;
                    recursiveFileCreator.OutputFolder = Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "Controllers");
                    recursiveFileCreator.Model = validmodel.Model;
                    recursiveFileCreator.TemplatesFolder = Path.Combine(resourceFolder, "Controllers");
                    recursiveFileCreator.ProjectName = moduleProject.Name;
                    recursiveFileCreator.Run();
                    foreach (string file in recursiveFileCreator.CreatedFiles)
                    {
                        AddFileToProjectIfNecessary(moduleProject, file);
                    }

                    if (Directory.Exists(Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "Views")) == false)
                    	Directory.CreateDirectory(Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "Views"));
                    
                    if (Directory.Exists(Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "Views\\Database")) == false)
                    	Directory.CreateDirectory(Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "Views\\Database"));
                    
                    IMultiTemplateCreator viewTemplateCreator = new MultiTemplateFileCreator();
                    viewTemplateCreator.CreateFolders = true;
                    viewTemplateCreator.ForceOverwrite = true;
                    viewTemplateCreator.OutputFolder = String.Format("{0}\\Views\\Database", Path.GetDirectoryName(moduleProject.FullName));
                    viewTemplateCreator.Model = validmodel.Model;
                    viewTemplateCreator.TemplatesFolder = resourceFolder + "\\Database";
                    viewTemplateCreator.Templates.Add("List");
                    viewTemplateCreator.Templates.Add("New");
                    viewTemplateCreator.Templates.Add("Detail");
                    viewTemplateCreator.Templates.Add("Edit");
                    viewTemplateCreator.Templates.Add("Index");
                    viewTemplateCreator.Templates.Add("Help");
                    viewTemplateCreator.ProjectName = moduleProject.Name;
                    viewTemplateCreator.Run();

                    foreach (string file in viewTemplateCreator.CreatedFiles)
                    {
                        AddFileToProjectIfNecessary(moduleProject, file);
                    }

                    sbar.Text = "Generated the database view templates...";
                    Application.DoEvents();

                    
                    if (Directory.Exists(Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "Views\\Admin")) == false)
                    	Directory.CreateDirectory(Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "Views\\Admin"));
                    
                    viewTemplateCreator = new MultiTemplateFileCreator();
                    viewTemplateCreator.CreateFolders = true;
                    viewTemplateCreator.ForceOverwrite = getOverwrite("view");
                    viewTemplateCreator.OutputFolder = String.Format("{0}\\Views\\Admin", Path.GetDirectoryName(moduleProject.FullName));
                    viewTemplateCreator.Model = validmodel.Model;
                    viewTemplateCreator.TemplatesFolder = resourceFolder + "\\Admin";
                    viewTemplateCreator.Templates.Add("List");
                    viewTemplateCreator.Templates.Add("New");
                    viewTemplateCreator.Templates.Add("Detail");
                    viewTemplateCreator.Templates.Add("Edit");
                    viewTemplateCreator.Templates.Add("Default");
                    viewTemplateCreator.Templates.Add("Help");
                    viewTemplateCreator.ProjectName = moduleProject.Name;
                    viewTemplateCreator.Run();

                    foreach (string file in viewTemplateCreator.CreatedFiles)
                    {
                        AddFileToProjectIfNecessary(moduleProject, file);
                    }

                    sbar.Text = "Generated the admin view templates...";
                    Application.DoEvents();
                    
                    recursiveFileCreator = new RecursiveFileCreator();
                    recursiveFileCreator.CreateFolders = true;
                    recursiveFileCreator.ForceOverwrite = false;
                    recursiveFileCreator.OutputFolder = String.Format("{0}\\Views", Path.GetDirectoryName(moduleProject.FullName)); 
                    recursiveFileCreator.Model = validmodel.Model;
                    recursiveFileCreator.TemplatesFolder = Path.Combine(resourceFolder, "Views");
                    recursiveFileCreator.ProjectName = moduleProject.Name;
                    recursiveFileCreator.Run();

                    foreach (string file in recursiveFileCreator.CreatedFiles)
                    {
                        AddFileToProjectIfNecessary(moduleProject, file);
                    }

                    BuildIncrementer bi = new BuildIncrementer(modelItem.DTE);
                    bi.IncrementBuildNumber();
                    string revision = bi.RevisionNumber;

                    if (Directory.Exists(Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "SQL")) == false)
                    	Directory.CreateDirectory(Path.Combine(Path.GetDirectoryName(moduleProject.FullName), "SQL"));
                    
                    ISingleTemplateCreator dbCreator = new SingleFileCreator();
                    dbCreator.CreateFolders = true;
                    dbCreator.ForceOverwrite = true;
                    dbCreator.Properties = validmodel.Properties;
                    dbCreator.OutputFolder = String.Format("{0}\\SQL", Path.GetDirectoryName(moduleProject.FullName));
                    dbCreator.Model = validmodel.Model;
                    dbCreator.TemplatesFolder = resourceFolder;
                    dbCreator.Template = "NeoCreateDb.sql.vtl";
                    dbCreator.ProjectName = "CreateDB";
                    dbCreator.Run();

                    foreach (string file in dbCreator.CreatedFiles)
                    {
                        //AddFileToProjectFolder(moduleProject, "SQL", file);
                        AddFileToProjectIfNecessary(moduleProject, file);
                    }

                    sbar.Text = "Generated the sql file...";
                    Application.DoEvents();

                    sbar.Text = "Webbness FrameWork has completed revision " + revision + ".";
                    Application.DoEvents();
                }
                catch (Exception e)
                {
                    MessageBox.Show(e.Message, "WebbnessCodeGen");
                }
            }
            else
            {
                MessageBox.Show("The model file is invalid. Check the generated file for more information.", "WebbnessCodeGen");
                writer.WriteLine("/* The model file is invalid: {0}\n*/", validmodel.Message);
            }
            output = Encoding.ASCII.GetBytes(writer.ToString());
            writer.Close();

            return output;
        }

        private void processOverwrites(string overwrites)
        {
            _overwriteArray = overwrites.Split('|');
        }

        private bool getOverwrite(string name)
        {
            foreach (String s in _overwriteArray)
            {
                if (s.Equals(name, StringComparison.InvariantCultureIgnoreCase))
                    return true;
            }
            return false;
        }

        private void AddFileToProjectIfNecessary(Project project, string filename)
        {
            if (ItemsContainFile(project.ProjectItems, filename) == false)
                project.ProjectItems.AddFromFile(filename);
        }

        private void AddFileToProjectFolder(Project project, string folder, string filename)
        {
            foreach (ProjectItem projectItem in project.ProjectItems)
            {
                if (IsFolder(projectItem))
                {
                    if (projectItem.Name.Equals(folder, StringComparison.InvariantCultureIgnoreCase))
                    {
                        if (projectItem.SubProject == null)
                        {
                            projectItem.ProjectItems.AddFromFile(filename);
                            return;
                        }
                        else
                        {
                            if (projectItem.SubProject.ProjectItems.Count > 0)
                            {
                                foreach (ProjectItem subItem in projectItem.SubProject.ProjectItems)
                                {
                                    Console.WriteLine(subItem);
                                    if (ProjectItemRepresentsFile(projectItem, filename) == false)
                                    {
                                        projectItem.SubProject.ProjectItems.AddFromFile(filename);
                                        return;
                                    }
                                }
                            }
                            else
                            {
                                projectItem.SubProject.ProjectItems.AddFromFile(filename);
                            }
                        }
                    }
                }
            }
            MessageBox.Show("The folder named '" + folder + "' was not found.", "Webbness Warning");
        }

        private bool ItemsContainFile(ProjectItems items, string filename)
        {
            foreach (ProjectItem projectItem in items)
            {
                if (IsFolder(projectItem))
                    return ItemsContainFile(projectItem.ProjectItems, filename);
                else
                    return ProjectItemRepresentsFile(projectItem, filename);
            }
            return false;
        }


        // if match check document full name in case it's the right filename wrong folder
        // NOTE: accessing the document can cause a COM exception
        private bool ProjectItemRepresentsFile(ProjectItem projectItem, string filename)
        {
            if (filename.ToLower() == projectItem.Name.ToLower())
            {
                try
                {
                    Document doc = projectItem.Document;
                    if (doc != null && doc.FullName != null)
                    {
                        if (doc.FullName.ToLower() == doc.FullName.ToLower())
                            return true;
                    }
                }
                catch (COMException)
                {
                    // ignore it
                }
            }
            return false;
        }

        public bool IsFolder(ProjectItem item)
        {
            foreach (string guid in folderGUIDs)
            {
                if (item.Kind.ToUpper() == guid)
                    return true;
            }
            return false;
        }
    }
}
